package com.xsf.pay.common.utils;

import cn.hutool.core.util.StrUtil;
import com.xsf.pay.common.constants.RedisConstant;
import com.xsf.pay.service.RedisService;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Component;

import javax.crypto.SecretKey;
import java.security.SecureRandom;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

/**
 * @author Administrator
 */
@Component
public class JwtTokenUtil {

    private SecretKey secretKey;

    @Value("${jwt.access-token.expiration}")
    private Long accessTokenExpiration;

    @Value("${jwt.refresh-token.expiration}")
    private Long refreshTokenExpiration;
    @Resource
    private RedisService redisService;
    private static final String UPPER = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    private static final String LOWER = "abcdefghijklmnopqrstuvwxyz";
    private static final String DIGITS = "0123456789";
    private static final String SPECIAL = "!@#$%^&*()-_=+[]{}|;:,.<>?/~`";
    private static final String ALL = UPPER + LOWER + DIGITS + SPECIAL;
    public JwtTokenUtil() {
        this.secretKey = Keys.secretKeyFor(SignatureAlgorithm.HS512);
    }
    private static final SecureRandom RANDOM = new SecureRandom();

    public static boolean isValidPassword(String password) {
        if (password == null || password.length() < 6 || password.length() > 15) {
            return false;
        }

        boolean hasUpper = false;
        boolean hasLower = false;
        boolean hasSpecial = false;

        for (char c : password.toCharArray()) {
            if (UPPER.indexOf(c) != -1) {
                hasUpper = true;
            } else if (LOWER.indexOf(c) != -1) {
                hasLower = true;
            } else if (SPECIAL.indexOf(c) != -1) {
                hasSpecial = true;
            }

            // 如果已经满足所有条件，可以提前返回
            if (hasUpper && hasLower && hasSpecial) {
                break;
            }
        }

        if (!hasUpper || !hasLower || !hasSpecial) {
            return false;
        }

        // 检查是否包含连续字符
        if (containsConsecutiveCharacters(password)) {
            return false;
        }

        return true;
    }
    public String generateAccessToken(String username) {
        Map<String, Object> claims = new HashMap<>();
        String accessToken = doGenerateToken(claims, username, accessTokenExpiration);
        redisService.set(StrUtil.format(RedisConstant.ACCESS_TOKEN_CACHE, username), accessToken, accessTokenExpiration, TimeUnit.MILLISECONDS);
        return accessToken;
    }

    public String generateRefreshToken(String username) {
        Map<String, Object> claims = new HashMap<>();
        String accessToken = doGenerateToken(claims, username, refreshTokenExpiration);
        redisService.set(StrUtil.format(RedisConstant.REFRESH_TOKEN_CACHE, username), accessToken, refreshTokenExpiration, TimeUnit.MILLISECONDS);
        return accessToken;
    }

    private String doGenerateToken(Map<String, Object> claims, String subject, Long expiration) {
        final Date createdDate = new Date();
        final Date expirationDate = new Date(createdDate.getTime() + expiration);
        return Jwts.builder()
                .setClaims(claims)
                .setSubject(subject)
                .setIssuedAt(createdDate)
                .setExpiration(expirationDate)
                .signWith(SignatureAlgorithm.HS512, secretKey)
                .compact();
    }

    public Boolean validateAccessToken(String token) {
        return validateToken(token, accessTokenExpiration);
    }

    public Boolean validateRefreshToken(String token) {
        return validateToken(token, accessTokenExpiration);
    }

    private Boolean validateToken(String token, Long expiration) {
        final String username = getUsernameFromToken(token);
        final Date expirationDate = getExpirationDateFromToken(token);
        return (username != null && !isTokenExpired(token, expiration));
    }

    public String getUsernameFromToken(String token) {
        return getClaimFromToken(token, Claims::getSubject);
    }

    public Date getExpirationDateFromToken(String token) {
        return getClaimFromToken(token, Claims::getExpiration);
    }

    public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
        final Claims claims = getAllClaimsFromToken(token);
        return claimsResolver.apply(claims);
    }

    private Claims getAllClaimsFromToken(String token) {
        return Jwts.parser()
                .setSigningKey(secretKey)
                .build()
                .parseClaimsJws(token)
                .getBody();
    }

    private Boolean isTokenExpired(String token, Long expiration) {
        final Date expirationDate = getExpirationDateFromToken(token);
        return expirationDate.before(new Date());
    }

    private static boolean containsConsecutiveCharacters(String password) {
        for (int i = 0; i < password.length() - 2; i++) {
            char c1 = password.charAt(i);
            char c2 = password.charAt(i + 1);
            char c3 = password.charAt(i + 2);

            // 检查连续数字
            if (Character.isDigit(c1) && Character.isDigit(c2) && Character.isDigit(c3)) {
                if (c2 == c1 + 1 && c3 == c2 + 1) {
                    return true;
                }
            }

            // 检查连续小写字母
            if (LOWER.indexOf(c1) != -1 && LOWER.indexOf(c2) != -1 && LOWER.indexOf(c3) != -1) {
                if (LOWER.indexOf(c2) == LOWER.indexOf(c1) + 1 && LOWER.indexOf(c3) == LOWER.indexOf(c2) + 1) {
                    return true;
                }
            }

            // 检查连续大写字母
            if (UPPER.indexOf(c1) != -1 && UPPER.indexOf(c2) != -1 && UPPER.indexOf(c3) != -1) {
                if (UPPER.indexOf(c2) == UPPER.indexOf(c1) + 1 && UPPER.indexOf(c3) == UPPER.indexOf(c2) + 1) {
                    return true;
                }
            }

            // 检查连续特殊字符
            if (SPECIAL.indexOf(c1) != -1 && SPECIAL.indexOf(c2) != -1 && SPECIAL.indexOf(c3) != -1) {
                if (SPECIAL.indexOf(c2) == SPECIAL.indexOf(c1) + 1 && SPECIAL.indexOf(c3) == SPECIAL.indexOf(c2) + 1) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * 生成符合条件的密码
     *
     * @return
     */
    public static String generateRandomPassword() {
        int length = 6 + RANDOM.nextInt(10);

        StringBuilder password = new StringBuilder(length);
        boolean[] used = new boolean[ALL.length()];

        // Ensure at least one character from each category
        password.append(UPPER.charAt(RANDOM.nextInt(UPPER.length())));
        password.append(LOWER.charAt(RANDOM.nextInt(LOWER.length())));
        password.append(DIGITS.charAt(RANDOM.nextInt(DIGITS.length())));
        password.append(SPECIAL.charAt(RANDOM.nextInt(SPECIAL.length())));

        // Fill the rest of the password length with random characters from all categories
        for (int i = 4; i < length; i++) {
            int index;
            do {
                index = RANDOM.nextInt(ALL.length());
            } while (used[index]);
            password.append(ALL.charAt(index));
            used[index] = true;
        }

        // Shuffle the password to ensure randomness
        return shuffleString(password.toString());
    }

    private static String shuffleString(String input) {
        char[] characters = input.toCharArray();
        for (int i = 0; i < characters.length; i++) {
            int randomIndex = RANDOM.nextInt(characters.length);
            char temp = characters[i];
            characters[i] = characters[randomIndex];
            characters[randomIndex] = temp;
        }
        return new String(characters);
    }

    public static void main(String[] args) {
        String password = "123456";
        String encodedPassword = new BCryptPasswordEncoder().encode(password);
        System.out.println("Encoded Password: " + encodedPassword);
    }

}

